﻿using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using CSScriptLibrary;
using RoMBot.Classes;
using RoMBot.Database;
using RoMBot.Properties;
using RoMBot.Scripting;

namespace RoMBot
{
    public enum PawnClass
    {
        None = -1,
        NotDefined = 0,
        Warrior = 1,
        Scout = 2,
        Rogue = 3,
        Mage = 4,
        Priest = 5,
        Knight = 6,
        Warden = 7,
        Druid = 8,
        Warlock = 9,
        Champion = 10
    }

    public class Profile
    {
        public const string BaseScriptDef =
                @"namespace RoMBot
            {{
                using System;
                using System.Diagnostics;
                using System.Collections.Generic;
                using RoMBot.Classes;
                using RoMBot.Database;
                using RoMBot.Waypoints;

                public class PlayerScript
                {{
                    public Bot Bot {{ get{{ return Bot.Instance; }} }}
                    public Player Player {{ get{{ return Bot.Player; }} }}
                    public Func<string, object> RoMScript {{ get{{ return Helpers.RoMScript; }} }}
                    public object RetVal {{ get; set; }}

                    public object Execute( object param = null )
                    {{
                        {0}
                        return null;
                    }}
                }}
            }}";

        private static readonly Dictionary<string, IScriptBase> Scripts = new Dictionary<string, IScriptBase>();
        //private static readonly Dictionary<string, string> ScriptsDef = new Dictionary<string, string>();
        private readonly List<string> friends = new List<string>();
        private readonly List<string> mobs = new List<string>();
        private readonly Dictionary<string, object> options = new Dictionary<string, object>();
        private Dictionary<string, Skill> skills = new Dictionary<string, Skill>();

        static Profile()
        {
            //Setup deafault keys
            HotKeys.MOVE_FORWARD.Key = Key.VK_W;
            HotKeys.ROTATE_LEFT.Key = Key.VK_Q;
            HotKeys.ROTATE_RIGHT.Key = Key.VK_E;
            HotKeys.MOVE_BACKWARD.Key = Key.VK_S;
            HotKeys.STRAFF_LEFT.Key = Key.VK_A;
            HotKeys.STRAFF_RIGHT.Key = Key.VK_D;
            HotKeys.JUMP.Key = Key.VK_SPACE;
        }

        internal Profile() { }

        public Profile( string file )
        {
            this.Load( file );
        }

        private ConsumableType reloadAmmuntionType = ConsumableType.None;
        public bool ReloadAmmunition
        {
            get
            {
                return reloadAmmuntionType != ConsumableType.None;
            }
        }

        public ConsumableType ReloadAmmunitionType
        {
            get { return reloadAmmuntionType; }
            internal set { reloadAmmuntionType = value; }
        }

        private void ResetOptions()
        {
            reloadAmmuntionType = ConsumableType.None;

            this.options.Clear();
            this.options.Add( "SKILL_GLOBALCOOLDOWN", 1000 );
            this.options.Add( "SKILL_USE_PRIOR", 30 );
            this.options.Add( "INV_MAX_SLOTS", 60 );
            this.options.Add( "QUICK_TURN", true );
            this.options.Add( "MAX_TARGET_DISTANCE", 200 );
            this.options.Add( "LOOT", true );
            this.options.Add( "LOOT_ALL", true );
            this.options.Add( "LOOT_TIME", 2000 );
            this.options.Add( "LOOT_AGAIN", 2000 );
            this.options.Add( "LOOT_IN_COMBAT", true );
            this.options.Add( "LOOT_IGNORE_LIST_SIZE", 5 );
            this.options.Add( "DOT_PERCENT", 90 );
            this.options.Add( "WAYPOINT_PASS", 100 );
            this.options.Add( "WAYPOINT_PASS_DEGR", 90 );
            this.options.Add( "WP_NO_STOP", true );
            this.options.Add( "MAX_FIGHT_TIME", 15 );
            this.options.Add( "MAX_SKILLUSE_NODMG", 4 );
            this.options.Add( "COMBAT_DISTANCE", 50 );

            this.options.Add( "HARVEST_WOOD", true );
            this.options.Add( "HARVEST_HERB", true );
            this.options.Add( "HARVEST_ORE", true );
            this.options.Add( "HARVEST_DISTANCE", 200 );
        }

        protected string Class1Name
        {
            get { return Bot.Player.Class1.ToString().ToLower(); }
        }

        protected string Class2Name
        {
            get { return Bot.Player.Class2.ToString().ToLower(); }
        }

        public object this[ string name ]
        {
            get { return this.options[ name ]; }
            set
            {
                if ( this.options.ContainsKey( name ) )
                {
                    var old = this.options[ name ];
                    if ( old != value )
                    {
                        Bot.Printf( Resources.ChangedProfileOption, name, old, value );
                    }
                }
                this.options[ name ] = value;
            }
        }

        public List<string> Friends
        {
            get { return this.friends; }
        }

        public List<string> Mobs
        {
            get { return this.mobs; }
        }

        public Dictionary<string, Skill> Skills
        {
            get { return this.skills; }
            set { this.skills = value; }
        }

        private string fileName;
        private XDocument doc;
        public void Load( string file, bool force = false )
        {
            if ( !file.ToLower().EndsWith( ".xml" ) )
            {
                file += ".xml";
            }
            if ( !file.StartsWith( Bot.DataPath ) )
            {
                file = Bot.DataPath + "Profiles\\" + file;
            }
            if ( file != fileName )
            {
                fileName = file;
            }
            else
            {
                if ( !force )
                {
                    return;
                }
            }

            if ( !File.Exists( file ) && !string.IsNullOrEmpty( Bot.ForcedProfile ) )
            {
                file = Bot.ForcedProfile;
            }

            //ScriptsDef.Clear();
            Scripts.Clear();
            this.mobs.Clear();
            this.friends.Clear();
            this.skills.Clear();
            ResetOptions();
            doc = XDocument.Load( file );

            var profileElement = doc.Element( "profile" );
            if ( profileElement != null )
            {
                var optionsElement = profileElement.Element( "options" );
                if ( optionsElement != null )
                {
                    var opts = from d in optionsElement.Elements( "option" )
                               select new
                                      {
                                          Name = d.Attribute( "name" ).Value,
                                          d.Attribute( "value" ).Value
                                      };

                    foreach ( var option in opts )
                    {
                        if ( !this.options.ContainsKey( option.Name ) )
                        {
                            this.options.Add( option.Name, option.Value );
                        }
                        else
                        {
                            this.options[ option.Name ] = option.Value;
                        }
                    }
                }

                ProcessSkills();

                // Friends
                var friendsElement = profileElement.Element( "friends" );
                if ( friendsElement != null )
                {
                    var profileFriends = from d in friendsElement.Elements( "friend" )
                                         where d.Attribute( "name" ) != null &&
                                         !string.IsNullOrWhiteSpace( d.Attribute( "name" ).Value )
                                         select d.Attribute( "name" ).Value;

                    this.friends.AddRange( profileFriends );
                }

                // Mobs
                var mobsElement = profileElement.Element( "mobs" );
                if ( mobsElement != null )
                {
                    var profileMobs = from d in mobsElement.Elements( "mob" )
                                      where d.Attribute( "name" ) != null &&
                                      !string.IsNullOrWhiteSpace( d.Attribute( "name" ).Value )
                                      select d.Attribute( "name" ).Value;

                    this.mobs.AddRange( profileMobs );
                }

                
                if ( profileElement.Element( "onLoad" ) != null )
                {
                    var eventString = NormalizeEventString( profileElement.Element( "onLoad" ).Value );
                    if ( !string.IsNullOrEmpty( eventString ) )
                    {
                        var s = CSScript.LoadCode( string.Format( BaseScriptDef, eventString ), "System" ).GetHelper().CreateAndAlignToInterface<IScriptBase>( "RoMBot.PlayerScript" );
                        Scripts.Add( "OnLoad", s );
                    }
                }

                if ( profileElement.Element( "onLevelUp" ) != null )
                {
                    var eventString = NormalizeEventString( profileElement.Element( "onLevelUp" ).Value );
                    if ( !string.IsNullOrEmpty( eventString ) )
                    {
                        var s = CSScript.LoadCode( string.Format( BaseScriptDef, eventString ), "System" ).GetHelper().CreateAndAlignToInterface<IScriptBase>( "RoMBot.PlayerScript" );
                        Scripts.Add( "OnLevelUp", s );
                    }
                }

                if ( profileElement.Element( "onBeforeSkillCast" ) != null )
                {
                    var eventString = NormalizeEventString( profileElement.Element( "onBeforeSkillCast" ).Value );
                    if ( !string.IsNullOrEmpty( eventString ) )
                    {
                        var s = CSScript.LoadCode( string.Format( BaseScriptDef, eventString ), "System" ).GetHelper().CreateAndAlignToInterface<IScriptBase>( "RoMBot.PlayerScript" );
                        Scripts.Add( "OnBeforeSkillCast", s );
                    }
                }

                if ( profileElement.Element( "onSkillCast" ) != null )
                {
                    var eventString = NormalizeEventString( profileElement.Element( "onSkillCast" ).Value );
                    if ( !string.IsNullOrEmpty( eventString ) )
                    {
                        var s = CSScript.LoadCode( string.Format( BaseScriptDef, eventString ), "System" ).GetHelper().CreateAndAlignToInterface<IScriptBase>( "RoMBot.PlayerScript" );
                        Scripts.Add( "OnSkillCast", s );
                    }
                }

                if ( profileElement.Element( "onLeaveCombat" ) != null )
                {
                    var eventString = NormalizeEventString( profileElement.Element( "onLeaveCombat" ).Value );
                    if ( !string.IsNullOrEmpty( eventString ) )
                    {
                        var s = CSScript.LoadCode( string.Format( BaseScriptDef, eventString ), "System" ).GetHelper().CreateAndAlignToInterface<IScriptBase>( "RoMBot.PlayerScript" );
                        Scripts.Add( "OnLeaveCombat", s );
                    }
                }

                if ( profileElement.Element( "onUnstickFailure" ) != null )
                {
                    var eventString = NormalizeEventString( profileElement.Element( "onUnstickFailure" ).Value );
                    if ( !string.IsNullOrEmpty( eventString ) )
                    {
                        var s = CSScript.LoadCode( string.Format( BaseScriptDef, eventString ), "System" ).GetHelper().CreateAndAlignToInterface<IScriptBase>( "RoMBot.PlayerScript" );
                        Scripts.Add( "OnUnstickFailure", s );
                    }
                }
            }

            if ( !this.options.ContainsKey( "COMBAT_TYPE" ) )
            {
                this.options[ "COMBAT_TYPE" ] = "";
            }

            if ( !OptionHasValue( "COMBAT_TYPE" ) && Bot.Player != null && Bot.Player.Exists )
            {
                var rangedPull = GetOptionValue<bool>( "COMBAT_RANGED_PULL" );
                switch ( Bot.Player.Class1 )
                {
                    case PawnClass.None:
                    case PawnClass.NotDefined:
                    case PawnClass.Warrior:
                    case PawnClass.Rogue:
                    case PawnClass.Knight:
                    case PawnClass.Champion:
                    case PawnClass.Warden:
                        this.options[ "COMBAT_TYPE" ] = "melee";
                        var combatDistance = GetOptionValue<int>( "COMBAT_DISTANCE" );
                        if ( rangedPull )
                        {
                            if ( combatDistance < 155 )
                            {
                                this[ "COMBAT_DISTANCE" ] = 155;
                            }
                        }
                        else if ( combatDistance > 50 )
                        {
                            this[ "COMBAT_DISTANCE" ] = 50;
                        }
                        break;
                    case PawnClass.Scout:
                    case PawnClass.Mage:
                    case PawnClass.Priest:
                    case PawnClass.Druid:
                    case PawnClass.Warlock:
                        this.options[ "COMBAT_TYPE" ] = "ranged";
                        Bot.Player.RangedPull = true;
                        break;
                }
            }

        }

        internal void ProcessSkills()
        {
            if ( doc == null )
            {
                return;
            }
            var profileElement = doc.Element( "profile" );
            if ( profileElement == null )
            {
                return;
            }

            // Shared skills
            IEnumerable<Skill> profileSkills;
            var skillsElement = profileElement.Element( "skills" );
            if ( skillsElement != null )
            {
                profileSkills = from d in skillsElement.Elements( "skill" )
                                select new Skill( d );

                foreach ( var item in profileSkills.OrderByDescending( s => s.Priority ) )
                {
                    this.skills.Add( item.ID, item );
                }
            }

            //First class skills
            if ( Bot.Player != null && Bot.Player.Exists )
            {
                var class1SkillsElement = profileElement.Element( "skills_" + this.Class1Name );
                if ( class1SkillsElement != null )
                {
                    profileSkills = from d in class1SkillsElement.Elements( "skill" )
                                    select new Skill( d );

                    foreach ( var item in profileSkills.OrderByDescending( s => s.Priority ) )
                    {
                        if ( !string.IsNullOrEmpty( item.ID ) )
                        {
                            if ( !this.skills.ContainsKey( item.ID ) )
                            {
                                this.skills.Add( item.ID, item );
                            }
                            else //Specific class skills override generics.
                            {
                                this.skills[ item.ID ] = item;
                            }
                        }
                    }
                }

                #region Class2 skills

                //else if ( _prof.Element( "skills_" + this.Class2Name ) != null )
                //{
                //    skills = from d in _prof.Element( "skills_" + this.Class2Name ).Elements( "skill" )
                //             select new Skill( d.Attribute( "name" ).Value )
                //                    {
                //                        HotKey = d.Attribute( "hotkey" ) != null ? Enum.IsDefined( typeof( Key ), d.Attribute( "hotkey" ).Value ) ? ( Key ) Enum.Parse( typeof( Key ), d.Attribute( "hotkey" ).Value ) : Key.VK_NONE : Key.VK_NONE,
                //                        Modifier = d.Attribute( "modifier" ) != null ? Enum.IsDefined( typeof( Key ), d.Attribute( "modifier" ).Value ) ? ( Key ) Enum.Parse( typeof( Key ), d.Attribute( "modifier" ).Value ) : Key.VK_NONE : Key.VK_NONE,
                //                        MaxUse = d.Attribute( "maxuse" ) != null ? Convert.ToInt32( d.Attribute( "maxuse" ).Value ) : -1,
                //                        RebuffCut = d.Attribute( "rebuffcut" ) != null ? Convert.ToSingle( d.Attribute( "rebuffcut" ).Value ) : 0f,
                //                        MaxHpPer = d.Attribute( "hpper" ) != null ? Convert.ToInt32( d.Attribute( "hpper" ).Value ) : 0,
                //                        InBattle = d.Attribute( "inBattle" ) != null ? Convert.ToBoolean( d.Attribute( "inBattle" ).Value ) : ( bool? ) null
                //                    };

                //    foreach ( Skill item in skills )
                //    {
                //        if ( !this._skills.ContainsKey( item.ID ) )
                //        {
                //            this._skills.Add( item.ID, item );
                //        }
                //    }
                //}

                #endregion

                Bot.Player.Profile = this;
                Database.Skills.ProcessPlayerSkills();
            }
        }

        private static string NormalizeEventString( string eventString )
        {
            if ( !string.IsNullOrEmpty( eventString ) )
            {
                eventString = eventString.Trim();
            }
            if ( string.IsNullOrWhiteSpace( eventString ) )
            {
                return null;
            }
            if ( !eventString.EndsWith( ";" ) )
            {
                eventString += ";";
            }
            return eventString;
        }

        public bool OptionHasValue( string optionName )
        {
            return this.options.ContainsKey( optionName ) && !string.IsNullOrWhiteSpace( this.options[ optionName ].ToString() );
        }

        public T GetOptionValue<T>( string name )
        {
            return this.options.ContainsKey( name ) ? Helpers.GetConvertedValue<T>( this.options[ name ] ) : default( T );
        }

        internal void OnLoad()
        {
            ExecuteEvent( "OnLoad" );
        }

        internal object OnBeforeSkillCast( Skill skill )
        {
            return ExecuteEvent( "OnBeforeSkillCast", skill );
        }

        internal object OnSkillCast( Skill skill )
        {
            return ExecuteEvent( "OnSkillCast", skill );
        }

        internal void OnLeaveCombat()
        {
            ExecuteEvent( "OnLeaveCombat" );
        }

        internal void OnLevelUp()
        {
            ExecuteEvent( "OnLevelUp" );
        }

        internal bool OnHarvest( GameObject o )
        {
            return ( bool ) ExecuteEvent( "OnHarvest", o );
        }

        internal void OnUnstickFailure()
        {
            ExecuteEvent( "OnLevelUp" );
        }

        private static object ExecuteEvent( string evento, object param = null )
        {
            return !Scripts.ContainsKey( evento ) ? null : Scripts[ evento ].Execute( param );
        }

        public bool HasOnLoad
        {
            get { return Scripts.ContainsKey( "OnLoad" ); }
        }

        public bool HasOnBeforeSkillCast
        {
            get { return Scripts.ContainsKey( "OnBeforeSkillCast" ); }
        }

        public bool HasOnUnstickFailure
        {
            get { return Scripts.ContainsKey( "OnUnstickFailure" ); }
        }

        public bool HasOnSkillCast
        {
            get { return Scripts.ContainsKey( "OnSkillCast" ); }
        }

        public bool HasOnLeaveCombat
        {
            get { return Scripts.ContainsKey( "OnLeaveCombat" ); }
        }

        public bool HasOnLevelUp
        {
            get { return Scripts.ContainsKey( "OnLevelUp" ); }
        }

        internal bool HasOnHarvest
        {
            get { return Scripts.ContainsKey( "OnHarvest" ); }
        }

        public static class HotKeys
        {
            public static HotKey Macro;
            public static HotKey AttackType;
            public static HotKey MOVE_FORWARD;
            public static HotKey ROTATE_LEFT;
            public static HotKey ROTATE_RIGHT;
            public static HotKey MOVE_BACKWARD;
            public static HotKey STRAFF_RIGHT;
            public static HotKey STRAFF_LEFT;
            public static HotKey JUMP;
        }

    }

    public struct HotKey
    {
#pragma warning disable 649
        public static readonly HotKey Empty;
#pragma warning restore 649
        public Key Key;
        public Key Modifier;
        public string Name;
    }
}